/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. *//* * Code to notify things that animate before a refresh, at an appropriate * refresh rate. (Perhaps temporary, until replaced by compositor.) * * Chrome and each tab have their own RefreshDriver, which in turn * hooks into one of a few global timer based on RefreshDriverTimer, * defined below. There are two main global timers -- one for active * animations, and one for inactive ones. These are implemented as * subclasses of RefreshDriverTimer; see below for a description of * their implementations. In the future, additional timer types may * implement things like blocking on vsync. */#ifdef XP_WIN#include<windows.h>// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have// to manually include it#include<mmsystem.h>#include"WinUtils.h"#endif#include"mozilla/ArrayUtils.h"#include"mozilla/AutoRestore.h"#include"mozilla/IntegerRange.h"#include"nsHostObjectProtocolHandler.h"#include"nsRefreshDriver.h"#include"nsITimer.h"#include"nsLayoutUtils.h"#include"nsPresContext.h"#include"nsComponentManagerUtils.h"#include"mozilla/Logging.h"#include"nsAutoPtr.h"#include"nsIDocument.h"#include"nsIXULRuntime.h"#include"jsapi.h"#include"nsContentUtils.h"#include"mozilla/PendingAnimationTracker.h"#include"mozilla/Preferences.h"#include"nsViewManager.h"#include"GeckoProfiler.h"#include"nsNPAPIPluginInstance.h"#include"mozilla/dom/Performance.h"#include"mozilla/dom/Selection.h"#include"mozilla/dom/WindowBinding.h"#include"mozilla/GeckoRestyleManager.h"#include"mozilla/RestyleManager.h"#include"mozilla/RestyleManagerInlines.h"#include"Layers.h"#include"imgIContainer.h"#include"mozilla/dom/ScriptSettings.h"#include"nsDocShell.h"#include"nsISimpleEnumerator.h"#include"nsJSEnvironment.h"#include"mozilla/Telemetry.h"#include"gfxPrefs.h"#include"BackgroundChild.h"#include"mozilla/ipc/PBackgroundChild.h"#include"nsIIPCBackgroundChildCreateCallback.h"#include"mozilla/layout/VsyncChild.h"#include"VsyncSource.h"#include"mozilla/VsyncDispatcher.h"#include"nsThreadUtils.h"#include"mozilla/Unused.h"#include"mozilla/TimelineConsumers.h"#include"nsAnimationManager.h"#include"nsIDOMEvent.h"#include"nsDisplayList.h"#ifdef MOZ_XUL#include"nsXULPopupManager.h"#endifusingnamespacemozilla;usingnamespacemozilla::widget;usingnamespacemozilla::ipc;usingnamespacemozilla::layout;staticmozilla::LazyLogModulesRefreshDriverLog("nsRefreshDriver");#define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))#define DEFAULT_THROTTLED_FRAME_RATE 1#define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000#define DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS 100// after 10 minutes, stop firing off inactive timers#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600// The number of seconds spent skipping frames because we are waiting for the compositor// before logging.#if defined(MOZ_ASAN)# define REFRESH_WAIT_WARNING 5#elif defined(DEBUG) && !defined(MOZ_VALGRIND)# define REFRESH_WAIT_WARNING 5#elif defined(DEBUG) && defined(MOZ_VALGRIND)# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5)#elif defined(MOZ_VALGRIND)# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1)#else# define REFRESH_WAIT_WARNING 1#endifnamespace{// `true` if we are currently in jank-critical mode.//// In jank-critical mode, any iteration of the event loop that takes// more than 16ms to compute will cause an ongoing animation to miss// frames.//// For simplicity, the current implementation assumes that we are in// jank-critical mode if and only if at least one vsync driver has// at least one observer.staticuint64_tsActiveVsyncTimers=0;// The latest value of process-wide jank levels.//// For each i, sJankLevels[i] counts the number of times delivery of// vsync to the main thread has been delayed by at least 2^i ms. Use// GetJankLevels to grab a copy of this array.uint64_tsJankLevels[12];// The number outstanding nsRefreshDrivers (that have been created but not// disconnected). When this reaches zero we will call// nsRefreshDriver::Shutdown.staticuint32_tsRefreshDriverCount=0;}namespacemozilla{/* * The base class for all global refresh driver timers. It takes care * of managing the list of refresh drivers attached to them and * provides interfaces for querying/setting the rate and actually * running a timer 'Tick'. Subclasses must implement StartTimer(), * StopTimer(), and ScheduleNextTick() -- the first two just * start/stop whatever timer mechanism is in use, and ScheduleNextTick * is called at the start of the Tick() implementation to set a time * for the next tick. */classRefreshDriverTimer{public:RefreshDriverTimer():mLastFireEpoch(0),mLastFireSkipped(false){}virtual~RefreshDriverTimer(){MOZ_ASSERT(mContentRefreshDrivers.Length()==0,"Should have removed all content refresh drivers from here by now!");MOZ_ASSERT(mRootRefreshDrivers.Length()==0,"Should have removed all root refresh drivers from here by now!");}virtualvoidAddRefreshDriver(nsRefreshDriver*aDriver){LOG("[%p] AddRefreshDriver %p",this,aDriver);boolstartTimer=mContentRefreshDrivers.IsEmpty()&&mRootRefreshDrivers.IsEmpty();if(IsRootRefreshDriver(aDriver)){NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver),"Adding a duplicate root refresh driver!");mRootRefreshDrivers.AppendElement(aDriver);}else{NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver),"Adding a duplicate content refresh driver!");mContentRefreshDrivers.AppendElement(aDriver);}if(startTimer){StartTimer();}}virtualvoidRemoveRefreshDriver(nsRefreshDriver*aDriver){LOG("[%p] RemoveRefreshDriver %p",this,aDriver);if(IsRootRefreshDriver(aDriver)){NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver),"RemoveRefreshDriver for a refresh driver that's not in the root refresh list!");mRootRefreshDrivers.RemoveElement(aDriver);}else{nsPresContext*pc=aDriver->GetPresContext();nsPresContext*rootContext=pc?pc->GetRootPresContext():nullptr;// During PresContext shutdown, we can't accurately detect// if a root refresh driver exists or not. Therefore, we have to// search and find out which list this driver exists in.if(!rootContext){if(mRootRefreshDrivers.Contains(aDriver)){mRootRefreshDrivers.RemoveElement(aDriver);}else{NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),"RemoveRefreshDriver without a display root for a driver that is not in the content refresh list");mContentRefreshDrivers.RemoveElement(aDriver);}}else{NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),"RemoveRefreshDriver for a driver that is not in the content refresh list");mContentRefreshDrivers.RemoveElement(aDriver);}}boolstopTimer=mContentRefreshDrivers.IsEmpty()&&mRootRefreshDrivers.IsEmpty();if(stopTimer){StopTimer();}}TimeStampMostRecentRefresh()const{returnmLastFireTime;}int64_tMostRecentRefreshEpochTime()const{returnmLastFireEpoch;}voidSwapRefreshDrivers(RefreshDriverTimer*aNewTimer){MOZ_ASSERT(NS_IsMainThread());for(nsRefreshDriver*driver:mContentRefreshDrivers){aNewTimer->AddRefreshDriver(driver);driver->mActiveTimer=aNewTimer;}mContentRefreshDrivers.Clear();for(nsRefreshDriver*driver:mRootRefreshDrivers){aNewTimer->AddRefreshDriver(driver);driver->mActiveTimer=aNewTimer;}mRootRefreshDrivers.Clear();aNewTimer->mLastFireEpoch=mLastFireEpoch;aNewTimer->mLastFireTime=mLastFireTime;}virtualTimeDurationGetTimerRate()=0;boolLastTickSkippedAnyPaints()const{returnmLastFireSkipped;}TimeStampGetIdleDeadlineHint(TimeStampaDefault){MOZ_ASSERT(NS_IsMainThread());TimeStampmostRecentRefresh=MostRecentRefresh();TimeDurationrefreshRate=GetTimerRate();TimeStampidleEnd=mostRecentRefresh+refreshRate;if(idleEnd+refreshRate*nsLayoutUtils::QuiescentFramesBeforeIdlePeriod()<TimeStamp::Now()){returnaDefault;}idleEnd=idleEnd-TimeDuration::FromMilliseconds(nsLayoutUtils::IdlePeriodDeadlineLimit());returnidleEnd<aDefault?idleEnd:aDefault;}protected:virtualvoidStartTimer()=0;virtualvoidStopTimer()=0;virtualvoidScheduleNextTick(TimeStampaNowTime)=0;boolIsRootRefreshDriver(nsRefreshDriver*aDriver){nsPresContext*pc=aDriver->GetPresContext();nsPresContext*rootContext=pc?pc->GetRootPresContext():nullptr;if(!rootContext){returnfalse;}returnaDriver==rootContext->RefreshDriver();}/* * Actually runs a tick, poking all the attached RefreshDrivers. * Grabs the "now" time via JS_Now and TimeStamp::Now(). */voidTick(){int64_tjsnow=JS_Now();TimeStampnow=TimeStamp::Now();Tick(jsnow,now);}voidTickRefreshDrivers(int64_taJsNow,TimeStampaNow,nsTArray<RefPtr<nsRefreshDriver>>&aDrivers){if(aDrivers.IsEmpty()){return;}nsTArray<RefPtr<nsRefreshDriver>>drivers(aDrivers);for(nsRefreshDriver*driver:drivers){// don't poke this driver if it's in test modeif(driver->IsTestControllingRefreshesEnabled()){continue;}TickDriver(driver,aJsNow,aNow);mLastFireSkipped=mLastFireSkipped||driver->mSkippedPaints;}}/* * Tick the refresh drivers based on the given timestamp. */voidTick(int64_tjsnow,TimeStampnow){ScheduleNextTick(now);mLastFireEpoch=jsnow;mLastFireTime=now;mLastFireSkipped=false;LOG("[%p] ticking drivers...",this);// RD is short for RefreshDriverAutoProfilerTracingtracing("Paint","RefreshDriverTick");TickRefreshDrivers(jsnow,now,mContentRefreshDrivers);TickRefreshDrivers(jsnow,now,mRootRefreshDrivers);LOG("[%p] done.",this);}staticvoidTickDriver(nsRefreshDriver*driver,int64_tjsnow,TimeStampnow){LOG(">> TickDriver: %p (jsnow: %"PRId64")",driver,jsnow);driver->Tick(jsnow,now);}int64_tmLastFireEpoch;boolmLastFireSkipped;TimeStampmLastFireTime;TimeStampmTargetTime;nsTArray<RefPtr<nsRefreshDriver>>mContentRefreshDrivers;nsTArray<RefPtr<nsRefreshDriver>>mRootRefreshDrivers;// useful callback for nsITimer-based derived classes, here// bacause of c++ protected shenanigansstaticvoidTimerTick(nsITimer*aTimer,void*aClosure){RefreshDriverTimer*timer=static_cast<RefreshDriverTimer*>(aClosure);timer->Tick();}};/* * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to * implement ScheduleNextTick and intelligently calculate the next time to tick, * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain * with its attempt at intelligent slack removal and such, so we don't do it. */classSimpleTimerBasedRefreshDriverTimer:publicRefreshDriverTimer{public:/* * aRate -- the delay, in milliseconds, requested between timer firings */explicitSimpleTimerBasedRefreshDriverTimer(doubleaRate){SetRate(aRate);mTimer=do_CreateInstance(NS_TIMER_CONTRACTID);}~SimpleTimerBasedRefreshDriverTimer()override{StopTimer();}// will take effect at next timer tickvirtualvoidSetRate(doubleaNewRate){mRateMilliseconds=aNewRate;mRateDuration=TimeDuration::FromMilliseconds(mRateMilliseconds);}doubleGetRate()const{returnmRateMilliseconds;}TimeDurationGetTimerRate()override{returnmRateDuration;}protected:voidStartTimer()override{// pretend we just fired, and we schedule the next tick normallymLastFireEpoch=JS_Now();mLastFireTime=TimeStamp::Now();mTargetTime=mLastFireTime+mRateDuration;uint32_tdelay=static_cast<uint32_t>(mRateMilliseconds);mTimer->InitWithNamedFuncCallback(TimerTick,this,delay,nsITimer::TYPE_ONE_SHOT,"SimpleTimerBasedRefreshDriverTimer::StartTimer");}voidStopTimer()override{mTimer->Cancel();}doublemRateMilliseconds;TimeDurationmRateDuration;RefPtr<nsITimer>mTimer;};/* * A refresh driver that listens to vsync events and ticks the refresh driver * on vsync intervals. We throttle the refresh driver if we get too many * vsync events and wait to catch up again. */classVsyncRefreshDriverTimer:publicRefreshDriverTimer{public:VsyncRefreshDriverTimer():mVsyncChild(nullptr){MOZ_ASSERT(XRE_IsParentProcess());MOZ_ASSERT(NS_IsMainThread());mVsyncObserver=newRefreshDriverVsyncObserver(this);RefPtr<mozilla::gfx::VsyncSource>vsyncSource=gfxPlatform::GetPlatform()->GetHardwareVsync();MOZ_ALWAYS_TRUE(mVsyncDispatcher=vsyncSource->GetRefreshTimerVsyncDispatcher());mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);mVsyncRate=vsyncSource->GetGlobalDisplay().GetVsyncRate();}explicitVsyncRefreshDriverTimer(VsyncChild*aVsyncChild):mVsyncChild(aVsyncChild){MOZ_ASSERT(!XRE_IsParentProcess());MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(mVsyncChild);mVsyncObserver=newRefreshDriverVsyncObserver(this);mVsyncChild->SetVsyncObserver(mVsyncObserver);mVsyncRate=mVsyncChild->GetVsyncRate();}TimeDurationGetTimerRate()override{if(mVsyncRate!=TimeDuration::Forever()){returnmVsyncRate;}if(mVsyncChild){// VsyncChild::VsyncRate() is a simple getter for the cached// hardware vsync rate. We depend on that// VsyncChild::GetVsyncRate() being called in the constructor// will result in a response with the actual vsync rate sooner// or later. Until that happens VsyncChild::VsyncRate() returns// TimeDuration::Forever() and we have to guess below.mVsyncRate=mVsyncChild->VsyncRate();}// If hardware queries fail / are unsupported, we have to just guess.returnmVsyncRate!=TimeDuration::Forever()?mVsyncRate:TimeDuration::FromMilliseconds(1000.0/60.0);}private:// Since VsyncObservers are refCounted, but the RefreshDriverTimer are// explicitly shutdown. We create an inner class that has the VsyncObserver// and is shutdown when the RefreshDriverTimer is deleted. The alternative is// to (a) make all RefreshDriverTimer RefCounted or (b) use different// VsyncObserver types.classRefreshDriverVsyncObserverfinal:publicVsyncObserver{public:explicitRefreshDriverVsyncObserver(VsyncRefreshDriverTimer*aVsyncRefreshDriverTimer):mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer),mRefreshTickLock("RefreshTickLock"),mRecentVsync(TimeStamp::Now()),mLastChildTick(TimeStamp::Now()),mVsyncRate(TimeDuration::Forever()),mProcessedVsync(true){MOZ_ASSERT(NS_IsMainThread());}classParentProcessVsyncNotifierfinal:publicRunnable,publicnsIRunnablePriority{public:ParentProcessVsyncNotifier(RefreshDriverVsyncObserver*aObserver,TimeStampaVsyncTimestamp):Runnable("VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::""ParentProcessVsyncNotifier"),mObserver(aObserver),mVsyncTimestamp(aVsyncTimestamp){}NS_DECL_ISUPPORTS_INHERITEDNS_IMETHODRun()override{MOZ_ASSERT(NS_IsMainThread());staticboolsCacheInitialized=false;staticboolsHighPriorityPrefValue=false;if(!sCacheInitialized){sCacheInitialized=true;Preferences::AddBoolVarCache(&sHighPriorityPrefValue,"vsync.parentProcess.highPriority",mozilla::BrowserTabsRemoteAutostart());}sHighPriorityEnabled=sHighPriorityPrefValue;mObserver->TickRefreshDriver(mVsyncTimestamp);returnNS_OK;}NS_IMETHODGetPriority(uint32_t*aPriority)override{*aPriority=sHighPriorityEnabled?nsIRunnablePriority::PRIORITY_HIGH:nsIRunnablePriority::PRIORITY_NORMAL;returnNS_OK;}private:~ParentProcessVsyncNotifier(){}RefPtr<RefreshDriverVsyncObserver>mObserver;TimeStampmVsyncTimestamp;staticmozilla::Atomic<bool>sHighPriorityEnabled;};boolNotifyVsync(TimeStampaVsyncTimestamp)override{if(!NS_IsMainThread()){MOZ_ASSERT(XRE_IsParentProcess());// Compress vsync notifications such that only 1 may run at a time// This is so that we don't flood the refresh driver with vsync messages// if the main thread is blocked for long periods of time{// scope lockMonitorAutoLocklock(mRefreshTickLock);mRecentVsync=aVsyncTimestamp;if(!mProcessedVsync){returntrue;}mProcessedVsync=false;}nsCOMPtr<nsIRunnable>vsyncEvent=newParentProcessVsyncNotifier(this,aVsyncTimestamp);NS_DispatchToMainThread(vsyncEvent);}else{mRecentVsync=aVsyncTimestamp;if(!mBlockUntil.IsNull()&&mBlockUntil>aVsyncTimestamp){if(mProcessedVsync){// Re-post vsync update as a normal priority runnable. This way// runnables already in normal priority queue get processed.mProcessedVsync=false;nsCOMPtr<nsIRunnable>vsyncEvent=NewRunnableMethod<>("RefreshDriverVsyncObserver::NormalPriorityNotify",this,&RefreshDriverVsyncObserver::NormalPriorityNotify);NS_DispatchToMainThread(vsyncEvent);}returntrue;}TickRefreshDriver(aVsyncTimestamp);}returntrue;}voidShutdown(){MOZ_ASSERT(NS_IsMainThread());mVsyncRefreshDriverTimer=nullptr;}voidOnTimerStart(){if(!XRE_IsParentProcess()){mLastChildTick=TimeStamp::Now();}}voidNormalPriorityNotify(){if(mLastProcessedTickInChildProcess.IsNull()||mRecentVsync>mLastProcessedTickInChildProcess){// mBlockUntil is for high priority vsync notifications only.mBlockUntil=TimeStamp();TickRefreshDriver(mRecentVsync);}mProcessedVsync=true;}private:~RefreshDriverVsyncObserver()=default;voidRecordTelemetryProbes(TimeStampaVsyncTimestamp){MOZ_ASSERT(NS_IsMainThread());#ifndef ANDROID /* bug 1142079 */if(XRE_IsParentProcess()){TimeDurationvsyncLatency=TimeStamp::Now()-aVsyncTimestamp;uint32_tsample=(uint32_t)vsyncLatency.ToMilliseconds();Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,sample);Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,sample);RecordJank(sample);}elseif(mVsyncRate!=TimeDuration::Forever()){TimeDurationcontentDelay=(TimeStamp::Now()-mLastChildTick)-mVsyncRate;if(contentDelay.ToMilliseconds()<0){// Vsyncs are noisy and some can come at a rate quicker than// the reported hardware rate. In those cases, consider that we have 0 delay.contentDelay=TimeDuration::FromMilliseconds(0);}uint32_tsample=(uint32_t)contentDelay.ToMilliseconds();Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,sample);Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,sample);RecordJank(sample);}else{// Request the vsync rate from the parent process. Might be a few vsyncs// until the parent responds.if(mVsyncRefreshDriverTimer){mVsyncRate=mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate();}}#endif}voidRecordJank(uint32_taJankMS){uint32_tduration=1/* ms */;for(size_ti=0;i<mozilla::ArrayLength(sJankLevels)&&duration<aJankMS;++i,duration*=2){sJankLevels[i]++;}}voidTickRefreshDriver(TimeStampaVsyncTimestamp){MOZ_ASSERT(NS_IsMainThread());RecordTelemetryProbes(aVsyncTimestamp);if(XRE_IsParentProcess()){MonitorAutoLocklock(mRefreshTickLock);aVsyncTimestamp=mRecentVsync;mProcessedVsync=true;}else{mLastChildTick=TimeStamp::Now();mLastProcessedTickInChildProcess=aVsyncTimestamp;}MOZ_ASSERT(aVsyncTimestamp<=TimeStamp::Now());// We might have a problem that we call ~VsyncRefreshDriverTimer() before// the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer// before use.if(mVsyncRefreshDriverTimer){mVsyncRefreshDriverTimer->RunRefreshDrivers(aVsyncTimestamp);}if(!XRE_IsParentProcess()){TimeDurationtickDuration=TimeStamp::Now()-mLastChildTick;mBlockUntil=aVsyncTimestamp+tickDuration;}}// VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will// be always available before Shutdown(). We can just use the raw pointer// here.VsyncRefreshDriverTimer*mVsyncRefreshDriverTimer;MonitormRefreshTickLock;TimeStampmRecentVsync;TimeStampmLastChildTick;TimeStampmLastProcessedTickInChildProcess;TimeStampmBlockUntil;TimeDurationmVsyncRate;boolmProcessedVsync;};// RefreshDriverVsyncObserver~VsyncRefreshDriverTimer()override{if(XRE_IsParentProcess()){mVsyncDispatcher->SetParentRefreshTimer(nullptr);mVsyncDispatcher=nullptr;}else{// Since the PVsyncChild actors live through the life of the process, just// send the unobserveVsync message to disable vsync event. We don't need// to handle the cleanup stuff of this actor. PVsyncChild::ActorDestroy()// will be called and clean up this actor.Unused<<mVsyncChild->SendUnobserve();mVsyncChild->SetVsyncObserver(nullptr);mVsyncChild=nullptr;}// Detach current vsync timer from this VsyncObserver. The observer will no// longer tick this timer.mVsyncObserver->Shutdown();mVsyncObserver=nullptr;}voidStartTimer()override{// Protect updates to `sActiveVsyncTimers`.MOZ_ASSERT(NS_IsMainThread());mLastFireEpoch=JS_Now();mLastFireTime=TimeStamp::Now();if(XRE_IsParentProcess()){mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);}else{Unused<<mVsyncChild->SendObserve();mVsyncObserver->OnTimerStart();}++sActiveVsyncTimers;}voidStopTimer()override{// Protect updates to `sActiveVsyncTimers`.MOZ_ASSERT(NS_IsMainThread());if(XRE_IsParentProcess()){mVsyncDispatcher->SetParentRefreshTimer(nullptr);}else{Unused<<mVsyncChild->SendUnobserve();}MOZ_ASSERT(sActiveVsyncTimers>0);--sActiveVsyncTimers;}voidScheduleNextTick(TimeStampaNowTime)override{// Do nothing since we just wait for the next vsync from// RefreshDriverVsyncObserver.}voidRunRefreshDrivers(TimeStampaTimeStamp){int64_tjsnow=JS_Now();TimeDurationdiff=TimeStamp::Now()-aTimeStamp;int64_tvsyncJsNow=jsnow-diff.ToMicroseconds();Tick(vsyncJsNow,aTimeStamp);}RefPtr<RefreshDriverVsyncObserver>mVsyncObserver;// Used for parent process.RefPtr<RefreshTimerVsyncDispatcher>mVsyncDispatcher;// Used for child process.// The mVsyncChild will be always available before VsncChild::ActorDestroy().// After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.RefPtr<VsyncChild>mVsyncChild;TimeDurationmVsyncRate;};// VsyncRefreshDriverTimerNS_IMPL_ISUPPORTS_INHERITED(VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::ParentProcessVsyncNotifier,Runnable,nsIRunnablePriority)mozilla::Atomic<bool>VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::ParentProcessVsyncNotifier::sHighPriorityEnabled(false);/** * Since the content process takes some time to setup * the vsync IPC connection, this timer is used * during the intial startup process. * During initial startup, the refresh drivers * are ticked off this timer, and are swapped out once content * vsync IPC connection is established. */classStartupRefreshDriverTimer:publicSimpleTimerBasedRefreshDriverTimer{public:explicitStartupRefreshDriverTimer(doubleaRate):SimpleTimerBasedRefreshDriverTimer(aRate){}protected:voidScheduleNextTick(TimeStampaNowTime)override{// Since this is only used for startup, it isn't super critical// that we tick at consistent intervals.TimeStampnewTarget=aNowTime+mRateDuration;uint32_tdelay=static_cast<uint32_t>((newTarget-aNowTime).ToMilliseconds());mTimer->InitWithNamedFuncCallback(TimerTick,this,delay,nsITimer::TYPE_ONE_SHOT,"StartupRefreshDriverTimer::ScheduleNextTick");mTargetTime=newTarget;}};/* * A RefreshDriverTimer for inactive documents. When a new refresh driver is * added, the rate is reset to the base (normally 1s/1fps). Every time * it ticks, a single refresh driver is poked. Once they have all been poked, * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point, * the timer is quiet and doesn't tick (until something is added to it again). * * When a timer is removed, there is a possibility of another timer * being skipped for one cycle. We could avoid this by adjusting * mNextDriverIndex in RemoveRefreshDriver, but there's little need to * add that complexity. All we want is for inactive drivers to tick * at some point, but we don't care too much about how often. */classInactiveRefreshDriverTimerfinal:publicSimpleTimerBasedRefreshDriverTimer{public:explicitInactiveRefreshDriverTimer(doubleaRate):SimpleTimerBasedRefreshDriverTimer(aRate),mNextTickDuration(aRate),mDisableAfterMilliseconds(-1.0),mNextDriverIndex(0){}InactiveRefreshDriverTimer(doubleaRate,doubleaDisableAfterMilliseconds):SimpleTimerBasedRefreshDriverTimer(aRate),mNextTickDuration(aRate),mDisableAfterMilliseconds(aDisableAfterMilliseconds),mNextDriverIndex(0){}voidAddRefreshDriver(nsRefreshDriver*aDriver)override{RefreshDriverTimer::AddRefreshDriver(aDriver);LOG("[%p] inactive timer got new refresh driver %p, resetting rate",this,aDriver);// reset the timer, and start with the newly added one next time.mNextTickDuration=mRateMilliseconds;// we don't really have to start with the newly added one, but we may as well// not tick the old ones at the fastest rate any more than we need to.mNextDriverIndex=GetRefreshDriverCount()-1;StopTimer();StartTimer();}TimeDurationGetTimerRate()override{returnTimeDuration::FromMilliseconds(mNextTickDuration);}protected:uint32_tGetRefreshDriverCount(){returnmContentRefreshDrivers.Length()+mRootRefreshDrivers.Length();}voidStartTimer()override{mLastFireEpoch=JS_Now();mLastFireTime=TimeStamp::Now();mTargetTime=mLastFireTime+mRateDuration;uint32_tdelay=static_cast<uint32_t>(mRateMilliseconds);mTimer->InitWithNamedFuncCallback(TimerTickOne,this,delay,nsITimer::TYPE_ONE_SHOT,"InactiveRefreshDriverTimer::StartTimer");}voidStopTimer()override{mTimer->Cancel();}voidScheduleNextTick(TimeStampaNowTime)override{if(mDisableAfterMilliseconds>0.0&&mNextTickDuration>mDisableAfterMilliseconds){// We hit the time after which we should disable// inactive window refreshes; don't schedule anything// until we get kicked by an AddRefreshDriver call.return;}// double the next tick time if we've already gone through all of them onceif(mNextDriverIndex>=GetRefreshDriverCount()){mNextTickDuration*=2.0;mNextDriverIndex=0;}// this doesn't need to be precise; do a simple scheduleuint32_tdelay=static_cast<uint32_t>(mNextTickDuration);mTimer->InitWithNamedFuncCallback(TimerTickOne,this,delay,nsITimer::TYPE_ONE_SHOT,"InactiveRefreshDriverTimer::ScheduleNextTick");LOG("[%p] inactive timer next tick in %f ms [index %d/%d]",this,mNextTickDuration,mNextDriverIndex,GetRefreshDriverCount());}/* Runs just one driver's tick. */voidTickOne(){int64_tjsnow=JS_Now();TimeStampnow=TimeStamp::Now();ScheduleNextTick(now);mLastFireEpoch=jsnow;mLastFireTime=now;mLastFireSkipped=false;nsTArray<RefPtr<nsRefreshDriver>>drivers(mContentRefreshDrivers);drivers.AppendElements(mRootRefreshDrivers);size_tindex=mNextDriverIndex;if(index<drivers.Length()&&!drivers[index]->IsTestControllingRefreshesEnabled()){TickDriver(drivers[index],jsnow,now);mLastFireSkipped=mLastFireSkipped||drivers[index]->SkippedPaints();}mNextDriverIndex++;}staticvoidTimerTickOne(nsITimer*aTimer,void*aClosure){InactiveRefreshDriverTimer*timer=static_cast<InactiveRefreshDriverTimer*>(aClosure);timer->TickOne();}doublemNextTickDuration;doublemDisableAfterMilliseconds;uint32_tmNextDriverIndex;};// The PBackground protocol connection callback. It will be called when// PBackground is ready. Then we create the PVsync sub-protocol for our// vsync-base RefreshTimer.classVsyncChildCreateCallbackfinal:publicnsIIPCBackgroundChildCreateCallback{NS_DECL_ISUPPORTSpublic:VsyncChildCreateCallback(){MOZ_ASSERT(NS_IsMainThread());}staticvoidCreateVsyncActor(PBackgroundChild*aPBackgroundChild){MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(aPBackgroundChild);layout::PVsyncChild*actor=aPBackgroundChild->SendPVsyncConstructor();layout::VsyncChild*child=static_cast<layout::VsyncChild*>(actor);nsRefreshDriver::PVsyncActorCreated(child);}private:virtual~VsyncChildCreateCallback()=default;voidActorCreated(PBackgroundChild*aPBackgroundChild)override{MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(aPBackgroundChild);CreateVsyncActor(aPBackgroundChild);}voidActorFailed()override{MOZ_ASSERT(NS_IsMainThread());MOZ_CRASH("Failed To Create VsyncChild Actor");}};// VsyncChildCreateCallbackNS_IMPL_ISUPPORTS(VsyncChildCreateCallback,nsIIPCBackgroundChildCreateCallback)}// namespace mozillastaticRefreshDriverTimer*sRegularRateTimer;staticInactiveRefreshDriverTimer*sThrottledRateTimer;staticvoidCreateContentVsyncRefreshTimer(void*){MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(!XRE_IsParentProcess());// Create the PVsync actor child for vsync-base refresh timer.// PBackgroundChild is created asynchronously. If PBackgroundChild is still// unavailable, setup VsyncChildCreateCallback callback to handle the async// connect. We will still use software timer before PVsync ready, and change// to use hw timer when the connection is done. Please check// VsyncChildCreateCallback::CreateVsyncActor() and// nsRefreshDriver::PVsyncActorCreated().PBackgroundChild*backgroundChild=BackgroundChild::GetForCurrentThread();if(backgroundChild){// If we already have PBackgroundChild, create the// child VsyncRefreshDriverTimer here.VsyncChildCreateCallback::CreateVsyncActor(backgroundChild);return;}// Setup VsyncChildCreateCallback callbackRefPtr<nsIIPCBackgroundChildCreateCallback>callback=newVsyncChildCreateCallback();if(NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))){MOZ_CRASH("PVsync actor create failed!");}}staticvoidCreateVsyncRefreshTimer(){MOZ_ASSERT(NS_IsMainThread());PodArrayZero(sJankLevels);// Sometimes, gfxPrefs is not initialized here. Make sure the gfxPrefs is// ready.gfxPrefs::GetSingleton();if(gfxPlatform::IsInLayoutAsapMode()){return;}if(XRE_IsParentProcess()){// Make sure all vsync systems are ready.gfxPlatform::GetPlatform();// In parent process, we don't need to use ipc. We can create the// VsyncRefreshDriverTimer directly.sRegularRateTimer=newVsyncRefreshDriverTimer();return;}// If this process is not created by NUWA, just create the vsync timer here.CreateContentVsyncRefreshTimer(nullptr);}staticuint32_tGetFirstFrameDelay(imgIRequest*req){nsCOMPtr<imgIContainer>container;if(NS_FAILED(req->GetImage(getter_AddRefs(container)))||!container){return0;}// If this image isn't animated, there isn't a first frame delay.int32_tdelay=container->GetFirstFrameDelay();if(delay<0)return0;returnstatic_cast<uint32_t>(delay);}/* static */voidnsRefreshDriver::Shutdown(){// clean up our timersdeletesRegularRateTimer;deletesThrottledRateTimer;sRegularRateTimer=nullptr;sThrottledRateTimer=nullptr;}/* static */int32_tnsRefreshDriver::DefaultInterval(){returnNSToIntRound(1000.0/gfxPlatform::GetDefaultFrameRate());}// Compute the interval to use for the refresh driver timer, in milliseconds.// outIsDefault indicates that rate was not explicitly set by the user// so we might choose other, more appropriate rates (e.g. vsync, etc)// layout.frame_rate=0 indicates "ASAP mode".// In ASAP mode rendering is iterated as fast as possible (typically for stress testing).// A target rate of 10k is used internally instead of special-handling 0.// Backends which block on swap/present/etc should try to not block// when layout.frame_rate=0 - to comply with "ASAP" as much as possible.doublensRefreshDriver::GetRegularTimerInterval(bool*outIsDefault)const{int32_trate=Preferences::GetInt("layout.frame_rate",-1);if(rate<0){rate=gfxPlatform::GetDefaultFrameRate();if(outIsDefault){*outIsDefault=true;}}else{if(outIsDefault){*outIsDefault=false;}}if(rate==0){rate=10000;}return1000.0/rate;}/* static */doublensRefreshDriver::GetThrottledTimerInterval(){int32_trate=Preferences::GetInt("layout.throttled_frame_rate",-1);if(rate<=0){rate=DEFAULT_THROTTLED_FRAME_RATE;}return1000.0/rate;}/* static */mozilla::TimeDurationnsRefreshDriver::GetMinRecomputeVisibilityInterval(){int32_tinterval=Preferences::GetInt("layout.visibility.min-recompute-interval-ms",-1);if(interval<=0){interval=DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS;}returnTimeDuration::FromMilliseconds(interval);}doublensRefreshDriver::GetRefreshTimerInterval()const{returnmThrottled?GetThrottledTimerInterval():GetRegularTimerInterval();}RefreshDriverTimer*nsRefreshDriver::ChooseTimer()const{if(mThrottled){if(!sThrottledRateTimer)sThrottledRateTimer=newInactiveRefreshDriverTimer(GetThrottledTimerInterval(),DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS*1000.0);returnsThrottledRateTimer;}if(!sRegularRateTimer){boolisDefault=true;doublerate=GetRegularTimerInterval(&isDefault);// Try to use vsync-base refresh timer first for sRegularRateTimer.CreateVsyncRefreshTimer();if(!sRegularRateTimer){sRegularRateTimer=newStartupRefreshDriverTimer(rate);}}returnsRegularRateTimer;}nsRefreshDriver::nsRefreshDriver(nsPresContext*aPresContext):mActiveTimer(nullptr),mPresContext(aPresContext),mRootRefresh(nullptr),mPendingTransaction(0),mCompletedTransaction(0),mFreezeCount(0),mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(GetThrottledTimerInterval())),mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),mThrottled(false),mNeedToRecomputeVisibility(false),mTestControllingRefreshes(false),mViewManagerFlushIsPending(false),mInRefresh(false),mWaitingForTransaction(false),mSkippedPaints(false),mResizeSuppressed(false),mWarningThreshold(REFRESH_WAIT_WARNING){MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(mPresContext,"Need a pres context to tell us to call Disconnect() later ""and decrement sRefreshDriverCount.");mMostRecentRefreshEpochTime=JS_Now();mMostRecentRefresh=TimeStamp::Now();mMostRecentTick=mMostRecentRefresh;mNextThrottledFrameRequestTick=mMostRecentTick;mNextRecomputeVisibilityTick=mMostRecentTick;++sRefreshDriverCount;}nsRefreshDriver::~nsRefreshDriver(){MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(ObserverCount()==mEarlyRunners.Length(),"observers, except pending selection scrolls, ""should have been unregistered");MOZ_ASSERT(!mActiveTimer,"timer should be gone");MOZ_ASSERT(!mPresContext,"Should have called Disconnect() and decremented ""sRefreshDriverCount!");if(mRootRefresh){mRootRefresh->RemoveRefreshObserver(this,FlushType::Style);mRootRefresh=nullptr;}}// Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh// for description.voidnsRefreshDriver::AdvanceTimeAndRefresh(int64_taMilliseconds){// ensure that we're removed from our driverStopTimer();if(!mTestControllingRefreshes){mMostRecentRefreshEpochTime=JS_Now();mMostRecentRefresh=TimeStamp::Now();mTestControllingRefreshes=true;if(mWaitingForTransaction){// Disable any refresh driver throttling when entering test modemWaitingForTransaction=false;mSkippedPaints=false;mWarningThreshold=REFRESH_WAIT_WARNING;}}mMostRecentRefreshEpochTime+=aMilliseconds*1000;mMostRecentRefresh+=TimeDuration::FromMilliseconds((double)aMilliseconds);mozilla::dom::AutoNoJSAPInojsapi;DoTick();}voidnsRefreshDriver::RestoreNormalRefresh(){mTestControllingRefreshes=false;EnsureTimerStarted(eAllowTimeToGoBackwards);mCompletedTransaction=mPendingTransaction;}TimeStampnsRefreshDriver::MostRecentRefresh()const{// In case of stylo traversal, we have already activated the refresh driver in// ServoRestyleManager::ProcessPendingRestyles().if(!ServoStyleSet::IsInServoTraversal()){const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();}returnmMostRecentRefresh;}int64_tnsRefreshDriver::MostRecentRefreshEpochTime()const{const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();returnmMostRecentRefreshEpochTime;}boolnsRefreshDriver::AddRefreshObserver(nsARefreshObserver*aObserver,FlushTypeaFlushType){ObserverArray&array=ArrayFor(aFlushType);boolsuccess=array.AppendElement(aObserver)!=nullptr;EnsureTimerStarted();returnsuccess;}boolnsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver*aObserver,FlushTypeaFlushType){ObserverArray&array=ArrayFor(aFlushType);returnarray.RemoveElement(aObserver);}voidnsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver*aObserver){mPostRefreshObservers.AppendElement(aObserver);}voidnsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver*aObserver){mPostRefreshObservers.RemoveElement(aObserver);}boolnsRefreshDriver::AddImageRequest(imgIRequest*aRequest){uint32_tdelay=GetFirstFrameDelay(aRequest);if(delay==0){mRequests.PutEntry(aRequest);}else{ImageStartData*start=mStartTable.LookupForAdd(delay).OrInsert([](){returnnewImageStartData();});start->mEntries.PutEntry(aRequest);}EnsureTimerStarted();returntrue;}voidnsRefreshDriver::RemoveImageRequest(imgIRequest*aRequest){// Try to remove from both places, just in case, because we can't tell// whether RemoveEntry() succeeds.mRequests.RemoveEntry(aRequest);uint32_tdelay=GetFirstFrameDelay(aRequest);if(delay!=0){ImageStartData*start=mStartTable.Get(delay);if(start){start->mEntries.RemoveEntry(aRequest);}}}voidnsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlagsaFlags){// FIXME: Bug 1346065: We should also assert the case where we have// STYLO_THREADS=1.MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal()||NS_IsMainThread(),"EnsureTimerStarted should be called only when we are not ""in servo traversal or on the main-thread");if(mTestControllingRefreshes)return;// will it already fire, and no other changes needed?if(mActiveTimer&&!(aFlags&eForceAdjustTimer))return;if(IsFrozen()||!mPresContext){// If we don't want to start it now, or we've been disconnected.StopTimer();return;}if(mPresContext->Document()->IsBeingUsedAsImage()){// Image documents receive ticks from clients' refresh drivers.// XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until// they receive refresh-driver ticks from their client docs (bug 1107252).nsIURI*uri=mPresContext->Document()->GetDocumentURI();if(!uri||!IsFontTableURI(uri)){MOZ_ASSERT(!mActiveTimer,"image doc refresh driver should never have its own timer");return;}}// We got here because we're either adjusting the time *or* we're// starting it for the first time. Add to the right timer,// prehaps removing it from a previously-set one.RefreshDriverTimer*newTimer=ChooseTimer();if(newTimer!=mActiveTimer){if(mActiveTimer)mActiveTimer->RemoveRefreshDriver(this);mActiveTimer=newTimer;mActiveTimer->AddRefreshDriver(this);}// When switching from an inactive timer to an active timer, the root// refresh driver is skipped due to being set to the content refresh// driver's timestamp. In case of EnsureTimerStarted is called from// ScheduleViewManagerFlush, we should avoid this behavior to flush// a paint in the same tick on the root refresh driver.if(aFlags&eNeverAdjustTimer){return;}// Since the different timers are sampled at different rates, when switching// timers, the most recent refresh of the new timer may be *before* the// most recent refresh of the old timer. However, the refresh driver time// should not go backwards so we clamp the most recent refresh time.//// The one exception to this is when we are restoring the refresh driver// from test control in which case the time is expected to go backwards// (see bug 1043078).mMostRecentRefresh=aFlags&eAllowTimeToGoBackwards?mActiveTimer->MostRecentRefresh():std::max(mActiveTimer->MostRecentRefresh(),mMostRecentRefresh);mMostRecentRefreshEpochTime=aFlags&eAllowTimeToGoBackwards?mActiveTimer->MostRecentRefreshEpochTime():std::max(mActiveTimer->MostRecentRefreshEpochTime(),mMostRecentRefreshEpochTime);}voidnsRefreshDriver::StopTimer(){if(!mActiveTimer)return;mActiveTimer->RemoveRefreshDriver(this);mActiveTimer=nullptr;}uint32_tnsRefreshDriver::ObserverCount()const{uint32_tsum=0;for(uint32_ti=0;i<ArrayLength(mObservers);++i){sum+=mObservers[i].Length();}// Even while throttled, we need to process layout and style changes. Style// changes can trigger transitions which fire events when they complete, and// layout changes can affect media queries on child documents, triggering// style changes, etc.sum+=mStyleFlushObservers.Length();sum+=mLayoutFlushObservers.Length();sum+=mPendingEvents.Length();sum+=mFrameRequestCallbackDocs.Length();sum+=mThrottledFrameRequestCallbackDocs.Length();sum+=mViewManagerFlushIsPending;sum+=mEarlyRunners.Length();returnsum;}uint32_tnsRefreshDriver::ImageRequestCount()const{uint32_tcount=0;for(autoiter=mStartTable.ConstIter();!iter.Done();iter.Next()){count+=iter.UserData()->mEntries.Count();}returncount+mRequests.Count();}nsRefreshDriver::ObserverArray&nsRefreshDriver::ArrayFor(FlushTypeaFlushType){switch(aFlushType){caseFlushType::Style:returnmObservers[0];caseFlushType::Layout:returnmObservers[1];caseFlushType::Display:returnmObservers[2];default:MOZ_CRASH("We don't track refresh observers for this flush type");}}/* * nsITimerCallback implementation */voidnsRefreshDriver::DoTick(){NS_PRECONDITION(!IsFrozen(),"Why are we notified while frozen?");NS_PRECONDITION(mPresContext,"Why are we notified after disconnection?");NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),"Shouldn't have a JSContext on the stack");if(mTestControllingRefreshes){Tick(mMostRecentRefreshEpochTime,mMostRecentRefresh);}else{Tick(JS_Now(),TimeStamp::Now());}}structDocumentFrameCallbacks{explicitDocumentFrameCallbacks(nsIDocument*aDocument):mDocument(aDocument){}nsCOMPtr<nsIDocument>mDocument;nsIDocument::FrameRequestCallbackListmCallbacks;};staticnsDocShell*GetDocShell(nsPresContext*aPresContext){returnstatic_cast<nsDocShell*>(aPresContext->GetDocShell());}staticboolHasPendingAnimations(nsIPresShell*aShell){nsIDocument*doc=aShell->GetDocument();if(!doc){returnfalse;}PendingAnimationTracker*tracker=doc->GetPendingAnimationTracker();returntracker&&tracker->HasPendingAnimations();}/** * Return a list of all the child docShells in a given root docShell that are * visible and are recording markers for the profilingTimeline */staticvoidGetProfileTimelineSubDocShells(nsDocShell*aRootDocShell,nsTArray<nsDocShell*>&aShells){if(!aRootDocShell){return;}RefPtr<TimelineConsumers>timelines=TimelineConsumers::Get();if(!timelines||timelines->IsEmpty()){return;}nsCOMPtr<nsISimpleEnumerator>enumerator;nsresultrv=aRootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll,nsIDocShell::ENUMERATE_BACKWARDS,getter_AddRefs(enumerator));if(NS_FAILED(rv)){return;}nsCOMPtr<nsIDocShell>curItem;boolhasMore=false;while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore))&&hasMore){nsCOMPtr<nsISupports>curSupports;enumerator->GetNext(getter_AddRefs(curSupports));curItem=do_QueryInterface(curSupports);if(!curItem||!curItem->GetRecordProfileTimelineMarkers()){continue;}nsDocShell*shell=static_cast<nsDocShell*>(curItem.get());boolisVisible=false;shell->GetVisibility(&isVisible);if(!isVisible){continue;}aShells.AppendElement(shell);}}staticvoidTakeFrameRequestCallbacksFrom(nsIDocument*aDocument,nsTArray<DocumentFrameCallbacks>&aTarget){aTarget.AppendElement(aDocument);aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);}voidnsRefreshDriver::DispatchPendingEvents(){// Swap out the current pending eventsnsTArray<PendingEvent>pendingEvents(Move(mPendingEvents));for(PendingEvent&event:pendingEvents){booldummy;event.mTarget->DispatchEvent(event.mEvent,&dummy);}}staticboolCollectDocuments(nsIDocument*aDocument,void*aDocArray){static_cast<AutoTArray<nsCOMPtr<nsIDocument>,32>*>(aDocArray)->AppendElement(aDocument);aDocument->EnumerateSubDocuments(CollectDocuments,aDocArray);returntrue;}voidnsRefreshDriver::DispatchAnimationEvents(){if(!mPresContext){return;}AutoTArray<nsCOMPtr<nsIDocument>,32>documents;CollectDocuments(mPresContext->Document(),&documents);for(uint32_ti=0;i<documents.Length();++i){nsIDocument*doc=documents[i];nsIPresShell*shell=doc->GetShell();if(!shell){continue;}RefPtr<nsPresContext>context=shell->GetPresContext();if(!context||context->RefreshDriver()!=this){continue;}context->TransitionManager()->SortEvents();context->AnimationManager()->SortEvents();// Dispatch transition events first since transitions conceptually sit// below animations in terms of compositing order.context->TransitionManager()->DispatchEvents();// Check that the presshell has not been destroyedif(context->GetPresShell()){context->AnimationManager()->DispatchEvents();}}}voidnsRefreshDriver::RunFrameRequestCallbacks(TimeStampaNowTime){// Grab all of our frame request callbacks up front.nsTArray<DocumentFrameCallbacks>frameRequestCallbacks(mFrameRequestCallbackDocs.Length()+mThrottledFrameRequestCallbackDocs.Length());// First, grab throttled frame request callbacks.{nsTArray<nsIDocument*>docsToRemove;// We always tick throttled frame requests if the entire refresh driver is// throttled, because in that situation throttled frame requests tick at the// same frequency as non-throttled frame requests.booltickThrottledFrameRequests=mThrottled;if(!tickThrottledFrameRequests&&aNowTime>=mNextThrottledFrameRequestTick){mNextThrottledFrameRequestTick=aNowTime+mThrottledFrameRequestInterval;tickThrottledFrameRequests=true;}for(nsIDocument*doc:mThrottledFrameRequestCallbackDocs){if(tickThrottledFrameRequests){// We're ticking throttled documents, so grab this document's requests.// We don't bother appending to docsToRemove because we're going to// clear mThrottledFrameRequestCallbackDocs anyway.TakeFrameRequestCallbacksFrom(doc,frameRequestCallbacks);}elseif(!doc->ShouldThrottleFrameRequests()){// This document is no longer throttled, so grab its requests even// though we're not ticking throttled frame requests right now. If// this is the first unthrottled document with frame requests, we'll// enter high precision mode the next time the callback is scheduled.TakeFrameRequestCallbacksFrom(doc,frameRequestCallbacks);docsToRemove.AppendElement(doc);}}// Remove all the documents we're ticking from// mThrottledFrameRequestCallbackDocs so they can be readded as needed.if(tickThrottledFrameRequests){mThrottledFrameRequestCallbackDocs.Clear();}else{// XXX(seth): We're using this approach to avoid concurrent modification// of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either// zero elements or a very small number, so this should be OK in practice.for(nsIDocument*doc:docsToRemove){mThrottledFrameRequestCallbackDocs.RemoveElement(doc);}}}// Now grab unthrottled frame request callbacks.for(nsIDocument*doc:mFrameRequestCallbackDocs){TakeFrameRequestCallbacksFrom(doc,frameRequestCallbacks);}// Reset mFrameRequestCallbackDocs so they can be readded as needed.mFrameRequestCallbackDocs.Clear();if(!frameRequestCallbacks.IsEmpty()){AutoProfilerTracingtracing("Paint","Scripts");for(constDocumentFrameCallbacks&docCallbacks:frameRequestCallbacks){// XXXbz Bug 863140: GetInnerWindow can return the outer// window in some cases.nsPIDOMWindowInner*innerWindow=docCallbacks.mDocument->GetInnerWindow();DOMHighResTimeStamptimeStamp=0;if(innerWindow&&innerWindow->IsInnerWindow()){mozilla::dom::Performance*perf=innerWindow->GetPerformance();if(perf){timeStamp=perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime);}// else window is partially torn down already}for(auto&callback:docCallbacks.mCallbacks){callback->Call(timeStamp);}}}}structRunnableWithDelay{nsCOMPtr<nsIRunnable>mRunnable;uint32_tmDelay;};staticAutoTArray<RunnableWithDelay,8>*sPendingIdleRunnables=nullptr;voidnsRefreshDriver::DispatchIdleRunnableAfterTick(nsIRunnable*aRunnable,uint32_taDelay){if(!sPendingIdleRunnables){sPendingIdleRunnables=newAutoTArray<RunnableWithDelay,8>();}RunnableWithDelayrwd={aRunnable,aDelay};sPendingIdleRunnables->AppendElement(rwd);}voidnsRefreshDriver::CancelIdleRunnable(nsIRunnable*aRunnable){if(!sPendingIdleRunnables){return;}for(uint32_ti=0;i<sPendingIdleRunnables->Length();++i){if((*sPendingIdleRunnables)[i].mRunnable==aRunnable){sPendingIdleRunnables->RemoveElementAt(i);break;}}if(sPendingIdleRunnables->IsEmpty()){deletesPendingIdleRunnables;sPendingIdleRunnables=nullptr;}}voidnsRefreshDriver::Tick(int64_taNowEpoch,TimeStampaNowTime){NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),"Shouldn't have a JSContext on the stack");if(nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()){NS_ERROR("Refresh driver should not run during plugin call!");// Try to survive this by just ignoring the refresh tick.return;}AUTO_PROFILER_LABEL("nsRefreshDriver::Tick",GRAPHICS);// We're either frozen or we were disconnected (likely in the middle// of a tick iteration). Just do nothing here, since our// prescontext went away.if(IsFrozen()||!mPresContext){return;}// We can have a race condition where the vsync timestamp// is before the most recent refresh due to a forced refresh.// The underlying assumption is that the refresh driver tick can only// go forward in time, not backwards. To prevent the refresh// driver from going back in time, just skip this tick and// wait until the next tick.if((aNowTime<=mMostRecentRefresh)&&!mTestControllingRefreshes){return;}TimeStamppreviousRefresh=mMostRecentRefresh;mMostRecentRefresh=aNowTime;mMostRecentRefreshEpochTime=aNowEpoch;if(IsWaitingForPaint(aNowTime)){// We're currently suspended waiting for earlier Tick's to// be completed (on the Compositor). Mark that we missed the paint// and keep waiting.return;}mMostRecentTick=aNowTime;if(mRootRefresh){mRootRefresh->RemoveRefreshObserver(this,FlushType::Style);mRootRefresh=nullptr;}mSkippedPaints=false;mWarningThreshold=1;nsCOMPtr<nsIPresShell>presShell=mPresContext->GetPresShell();if(!presShell||(ObserverCount()==0&&ImageRequestCount()==0)){// Things are being destroyed, or we no longer have any observers.// We don't want to stop the timer when observers are initially// removed, because sometimes observers can be added and removed// often depending on what other things are going on and in that// situation we don't want to thrash our timer. So instead we// wait until we get a Notify() call when we have no observers// before stopping the timer.StopTimer();return;}mResizeSuppressed=false;AutoRestore<bool>restoreInRefresh(mInRefresh);mInRefresh=true;AutoRestore<TimeStamp>restoreTickStart(mTickStart);mTickStart=TimeStamp::Now();gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();// We want to process any pending APZ metrics ahead of their positions// in the queue. This will prevent us from spending precious time// painting a stale displayport.if(gfxPrefs::APZPeekMessages()){nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages();}AutoTArray<nsCOMPtr<nsIRunnable>,16>earlyRunners;earlyRunners.SwapElements(mEarlyRunners);for(uint32_ti=0;i<earlyRunners.Length();++i){earlyRunners[i]->Run();}/* * The timer holds a reference to |this| while calling |Notify|. * However, implementations of |WillRefresh| are permitted to destroy * the pres context, which will cause our |mPresContext| to become * null. If this happens, we must stop notifying observers. */for(uint32_ti=0;i<ArrayLength(mObservers);++i){ObserverArray::EndLimitedIteratoretor(mObservers[i]);while(etor.HasMore()){RefPtr<nsARefreshObserver>obs=etor.GetNext();obs->WillRefresh(aNowTime);if(!mPresContext||!mPresContext->GetPresShell()){StopTimer();return;}}if(i==0){// This is the FlushType::Style case.DispatchAnimationEvents();DispatchPendingEvents();RunFrameRequestCallbacks(aNowTime);if(mPresContext&&mPresContext->GetPresShell()){Maybe<AutoProfilerTracing>tracingStyleFlush;AutoTArray<nsIPresShell*,16>observers;observers.AppendElements(mStyleFlushObservers);for(uint32_tj=observers.Length();j&&mPresContext&&mPresContext->GetPresShell();--j){// Make sure to not process observers which might have been removed// during previous iterations.nsIPresShell*shell=observers[j-1];if(!mStyleFlushObservers.RemoveElement(shell))continue;if(!tracingStyleFlush){tracingStyleFlush.emplace("Paint","Styles",Move(mStyleCause));mStyleCause=nullptr;}nsCOMPtr<nsIPresShell>shellKungFuDeathGrip(shell);shell->mObservingStyleFlushes=false;shell->FlushPendingNotifications(ChangesToFlush(FlushType::Style,false));// Inform the FontFaceSet that we ticked, so that it can resolve its// ready promise if it needs to (though it might still be waiting on// a layout flush).nsPresContext*presContext=shell->GetPresContext();if(presContext){presContext->NotifyFontFaceSetOnRefresh();}mNeedToRecomputeVisibility=true;}}}elseif(i==1){// This is the FlushType::Layout case.Maybe<AutoProfilerTracing>tracingLayoutFlush;AutoTArray<nsIPresShell*,16>observers;observers.AppendElements(mLayoutFlushObservers);for(uint32_tj=observers.Length();j&&mPresContext&&mPresContext->GetPresShell();--j){// Make sure to not process observers which might have been removed// during previous iterations.nsIPresShell*shell=observers[j-1];if(!mLayoutFlushObservers.RemoveElement(shell))continue;if(!tracingLayoutFlush){tracingLayoutFlush.emplace("Paint","Reflow",Move(mReflowCause));mReflowCause=nullptr;}nsCOMPtr<nsIPresShell>shellKungFuDeathGrip(shell);shell->mObservingLayoutFlushes=false;shell->mSuppressInterruptibleReflows=false;FlushTypeflushType=HasPendingAnimations(shell)?FlushType::Layout:FlushType::InterruptibleLayout;shell->FlushPendingNotifications(ChangesToFlush(flushType,false));// Inform the FontFaceSet that we ticked, so that it can resolve its// ready promise if it needs to.nsPresContext*presContext=shell->GetPresContext();if(presContext){presContext->NotifyFontFaceSetOnRefresh();}mNeedToRecomputeVisibility=true;}}// The pres context may be destroyed during we do the flushing.if(!mPresContext||!mPresContext->GetPresShell()){StopTimer();return;}}// Recompute approximate frame visibility if it's necessary and enough time// has passed since the last time we did it.if(mNeedToRecomputeVisibility&&!mThrottled&&aNowTime>=mNextRecomputeVisibilityTick&&!presShell->IsPaintingSuppressed()){mNextRecomputeVisibilityTick=aNowTime+mMinRecomputeVisibilityInterval;mNeedToRecomputeVisibility=false;presShell->ScheduleApproximateFrameVisibilityUpdateNow();}#ifdef MOZ_XUL// Update any popups that may need to be moved or hidden due to their// anchor changing.nsXULPopupManager*pm=nsXULPopupManager::GetInstance();if(pm){pm->UpdatePopupPositions(this);}#endifAutoTArray<nsCOMPtr<nsIDocument>,32>documents;CollectDocuments(mPresContext->Document(),&documents);for(uint32_ti=0;i<documents.Length();++i){nsIDocument*doc=documents[i];doc->UpdateIntersectionObservations();doc->ScheduleIntersectionObserverNotification();}/* * Perform notification to imgIRequests subscribed to listen * for refresh events. */for(autoiter=mStartTable.Iter();!iter.Done();iter.Next()){constuint32_t&delay=iter.Key();ImageStartData*data=iter.UserData();if(data->mStartTime){TimeStamp&start=*data->mStartTime;TimeDurationprev=previousRefresh-start;TimeDurationcurr=aNowTime-start;uint32_tprevMultiple=uint32_t(prev.ToMilliseconds())/delay;// We want to trigger images' refresh if we've just crossed over a// multiple of the first image's start time. If so, set the animation// start time to the nearest multiple of the delay and move all the// images in this table to the main requests table.if(prevMultiple!=uint32_t(curr.ToMilliseconds())/delay){mozilla::TimeStampdesired=start+TimeDuration::FromMilliseconds(prevMultiple*delay);BeginRefreshingImages(data->mEntries,desired);}}else{// This is the very first time we've drawn images with this time delay.// Set the animation start time to "now" and move all the images in this// table to the main requests table.mozilla::TimeStampdesired=aNowTime;BeginRefreshingImages(data->mEntries,desired);data->mStartTime.emplace(aNowTime);}}if(mRequests.Count()){// RequestRefresh may run scripts, so it's not safe to directly call it// while using a hashtable enumerator to enumerate mRequests in case// script modifies the hashtable. Instead, we build a (local) array of// images to refresh, and then we refresh each image in that array.nsCOMArray<imgIContainer>imagesToRefresh(mRequests.Count());for(autoiter=mRequests.Iter();!iter.Done();iter.Next()){nsISupportsHashKey*entry=iter.Get();autoreq=static_cast<imgIRequest*>(entry->GetKey());MOZ_ASSERT(req,"Unable to retrieve the image request");nsCOMPtr<imgIContainer>image;if(NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))){imagesToRefresh.AppendElement(image.forget());}}for(uint32_ti=0;i<imagesToRefresh.Length();i++){imagesToRefresh[i]->RequestRefresh(aNowTime);}}booldispatchRunnablesAfterTick=false;if(mViewManagerFlushIsPending){RefPtr<TimelineConsumers>timelines=TimelineConsumers::Get();nsTArray<nsDocShell*>profilingDocShells;GetProfileTimelineSubDocShells(GetDocShell(mPresContext),profilingDocShells);for(nsDocShell*docShell:profilingDocShells){// For the sake of the profile timeline's simplicity, this is flagged as// paint even if it includes creating display listsMOZ_ASSERT(timelines);MOZ_ASSERT(timelines->HasConsumer(docShell));timelines->AddMarkerForDocShell(docShell,"Paint",MarkerTracingType::START);}#ifdef MOZ_DUMP_PAINTINGif(nsLayoutUtils::InvalidationDebuggingIsEnabled()){printf_stderr("Starting ProcessPendingUpdates\n");}#endifmViewManagerFlushIsPending=false;RefPtr<nsViewManager>vm=mPresContext->GetPresShell()->GetViewManager();{PaintTelemetry::AutoRecordPaintrecord;vm->ProcessPendingUpdates();}#ifdef MOZ_DUMP_PAINTINGif(nsLayoutUtils::InvalidationDebuggingIsEnabled()){printf_stderr("Ending ProcessPendingUpdates\n");}#endiffor(nsDocShell*docShell:profilingDocShells){MOZ_ASSERT(timelines);MOZ_ASSERT(timelines->HasConsumer(docShell));timelines->AddMarkerForDocShell(docShell,"Paint",MarkerTracingType::END);}dispatchRunnablesAfterTick=true;}#ifndef ANDROID /* bug 1142079 */mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK,mTickStart);#endifnsTObserverArray<nsAPostRefreshObserver*>::ForwardIteratoriter(mPostRefreshObservers);while(iter.HasMore()){nsAPostRefreshObserver*observer=iter.GetNext();observer->DidRefresh();}NS_ASSERTION(mInRefresh,"Still in refresh");if(mPresContext->IsRoot()&&XRE_IsContentProcess()&&gfxPrefs::AlwaysPaint()){ScheduleViewManagerFlush();}if(dispatchRunnablesAfterTick&&sPendingIdleRunnables){AutoTArray<RunnableWithDelay,8>*runnables=sPendingIdleRunnables;sPendingIdleRunnables=nullptr;for(uint32_ti=0;i<runnables->Length();++i){NS_IdleDispatchToCurrentThread((*runnables)[i].mRunnable.forget(),(*runnables)[i].mDelay);}deleterunnables;}}voidnsRefreshDriver::BeginRefreshingImages(RequestTable&aEntries,mozilla::TimeStampaDesired){for(autoiter=aEntries.Iter();!iter.Done();iter.Next()){autoreq=static_cast<imgIRequest*>(iter.Get()->GetKey());MOZ_ASSERT(req,"Unable to retrieve the image request");mRequests.PutEntry(req);nsCOMPtr<imgIContainer>image;if(NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))){image->SetAnimationStartTime(aDesired);}}aEntries.Clear();}voidnsRefreshDriver::Freeze(){StopTimer();mFreezeCount++;}voidnsRefreshDriver::Thaw(){NS_ASSERTION(mFreezeCount>0,"Thaw() called on an unfrozen refresh driver");if(mFreezeCount>0){mFreezeCount--;}if(mFreezeCount==0){if(ObserverCount()||ImageRequestCount()){// FIXME: This isn't quite right, since our EnsureTimerStarted call// updates our mMostRecentRefresh, but the DoRefresh call won't run// and notify our observers until we get back to the event loop.// Thus MostRecentRefresh() will lie between now and the DoRefresh.RefPtr<nsRunnableMethod<nsRefreshDriver>>event=NewRunnableMethod("nsRefreshDriver::DoRefresh",this,&nsRefreshDriver::DoRefresh);nsPresContext*pc=GetPresContext();if(pc){pc->Document()->Dispatch("nsRefreshDriver::DoRefresh",TaskCategory::Other,event.forget());EnsureTimerStarted();}else{NS_ERROR("Thawing while document is being destroyed");}}}}voidnsRefreshDriver::FinishedWaitingForTransaction(){mWaitingForTransaction=false;if(mSkippedPaints&&!IsInRefresh()&&(ObserverCount()||ImageRequestCount())){AutoProfilerTracingtracing("Paint","RefreshDriverTick");DoRefresh();}mSkippedPaints=false;mWarningThreshold=1;}uint64_tnsRefreshDriver::GetTransactionId(){++mPendingTransaction;if(mPendingTransaction>=mCompletedTransaction+2&&!mWaitingForTransaction&&!mTestControllingRefreshes){mWaitingForTransaction=true;mSkippedPaints=false;mWarningThreshold=1;}returnmPendingTransaction;}uint64_tnsRefreshDriver::LastTransactionId()const{returnmPendingTransaction;}voidnsRefreshDriver::RevokeTransactionId(uint64_taTransactionId){MOZ_ASSERT(aTransactionId==mPendingTransaction);if(mPendingTransaction==mCompletedTransaction+2&&mWaitingForTransaction){MOZ_ASSERT(!mSkippedPaints,"How did we skip a paint when we're in the middle of one?");FinishedWaitingForTransaction();}mPendingTransaction--;}voidnsRefreshDriver::ClearPendingTransactions(){mCompletedTransaction=mPendingTransaction;mWaitingForTransaction=false;}voidnsRefreshDriver::ResetInitialTransactionId(uint64_taTransactionId){mCompletedTransaction=mPendingTransaction=aTransactionId;}mozilla::TimeStampnsRefreshDriver::GetTransactionStart(){returnmTickStart;}voidnsRefreshDriver::NotifyTransactionCompleted(uint64_taTransactionId){if(aTransactionId>mCompletedTransaction){if(mPendingTransaction>mCompletedTransaction+1&&mWaitingForTransaction){mCompletedTransaction=aTransactionId;FinishedWaitingForTransaction();}else{mCompletedTransaction=aTransactionId;}}}voidnsRefreshDriver::WillRefresh(mozilla::TimeStampaTime){mRootRefresh->RemoveRefreshObserver(this,FlushType::Style);mRootRefresh=nullptr;if(mSkippedPaints){DoRefresh();}}boolnsRefreshDriver::IsWaitingForPaint(mozilla::TimeStampaTime){if(mTestControllingRefreshes){returnfalse;}if(mWaitingForTransaction){if(mSkippedPaints&&aTime>(mMostRecentTick+TimeDuration::FromMilliseconds(mWarningThreshold*1000))){// XXX - Bug 1303369 - too many false positives.//gfxCriticalNote << "Refresh driver waiting for the compositor for "// << (aTime - mMostRecentTick).ToSeconds()// << " seconds.";mWarningThreshold*=2;}mSkippedPaints=true;returntrue;}// Try find the 'root' refresh driver for the current window and check// if that is waiting for a paint.nsPresContext*pc=GetPresContext();nsPresContext*rootContext=pc?pc->GetRootPresContext():nullptr;if(rootContext){nsRefreshDriver*rootRefresh=rootContext->RefreshDriver();if(rootRefresh&&rootRefresh!=this){if(rootRefresh->IsWaitingForPaint(aTime)){if(mRootRefresh!=rootRefresh){if(mRootRefresh){mRootRefresh->RemoveRefreshObserver(this,FlushType::Style);}rootRefresh->AddRefreshObserver(this,FlushType::Style);mRootRefresh=rootRefresh;}mSkippedPaints=true;returntrue;}}}returnfalse;}voidnsRefreshDriver::SetThrottled(boolaThrottled){if(aThrottled!=mThrottled){mThrottled=aThrottled;if(mActiveTimer){// We want to switch our timer type here, so just stop and// restart the timer.EnsureTimerStarted(eForceAdjustTimer);}}}/*static*/voidnsRefreshDriver::PVsyncActorCreated(VsyncChild*aVsyncChild){MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(!XRE_IsParentProcess());auto*vsyncRefreshDriverTimer=newVsyncRefreshDriverTimer(aVsyncChild);// If we are using software timer, swap current timer to// VsyncRefreshDriverTimer.if(sRegularRateTimer){sRegularRateTimer->SwapRefreshDrivers(vsyncRefreshDriverTimer);deletesRegularRateTimer;}sRegularRateTimer=vsyncRefreshDriverTimer;}voidnsRefreshDriver::DoRefresh(){// Don't do a refresh unless we're in a state where we should be refreshing.if(!IsFrozen()&&mPresContext&&mActiveTimer){DoTick();}}#ifdef DEBUGboolnsRefreshDriver::IsRefreshObserver(nsARefreshObserver*aObserver,FlushTypeaFlushType){ObserverArray&array=ArrayFor(aFlushType);returnarray.Contains(aObserver);}#endifvoidnsRefreshDriver::ScheduleViewManagerFlush(){NS_ASSERTION(mPresContext->IsRoot(),"Should only schedule view manager flush on root prescontexts");mViewManagerFlushIsPending=true;EnsureTimerStarted(eNeverAdjustTimer);}voidnsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument*aDocument){NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument)==mFrameRequestCallbackDocs.NoIndex&&mThrottledFrameRequestCallbackDocs.IndexOf(aDocument)==mThrottledFrameRequestCallbackDocs.NoIndex,"Don't schedule the same document multiple times");if(aDocument->ShouldThrottleFrameRequests()){mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);}else{mFrameRequestCallbackDocs.AppendElement(aDocument);}// make sure that the timer is runningEnsureTimerStarted();}voidnsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument*aDocument){mFrameRequestCallbackDocs.RemoveElement(aDocument);mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument);// No need to worry about restarting our timer in slack mode if it's already// running; that will happen automatically when it fires.}voidnsRefreshDriver::ScheduleEventDispatch(nsINode*aTarget,nsIDOMEvent*aEvent){mPendingEvents.AppendElement(PendingEvent{aTarget,aEvent});// make sure that the timer is runningEnsureTimerStarted();}voidnsRefreshDriver::CancelPendingEvents(nsIDocument*aDocument){for(autoi:Reversed(IntegerRange(mPendingEvents.Length()))){if(mPendingEvents[i].mTarget->OwnerDoc()==aDocument){mPendingEvents.RemoveElementAt(i);}}}/* static */TimeStampnsRefreshDriver::GetIdleDeadlineHint(TimeStampaDefault){MOZ_ASSERT(NS_IsMainThread());MOZ_ASSERT(!aDefault.IsNull());if(!sRegularRateTimer){returnaDefault;}// For computing idleness of refresh drivers we only care about// sRegularRateTimer, since we consider refresh drivers attached to// sThrottledRateTimer to be inactive. This implies that tasks// resulting from a tick on the sRegularRateTimer counts as being// busy but tasks resulting from a tick on sThrottledRateTimer// counts as being idle.returnsRegularRateTimer->GetIdleDeadlineHint(aDefault);}voidnsRefreshDriver::Disconnect(){MOZ_ASSERT(NS_IsMainThread());StopTimer();if(mPresContext){mPresContext=nullptr;if(--sRefreshDriverCount==0){Shutdown();}}}/* static */boolnsRefreshDriver::IsJankCritical(){MOZ_ASSERT(NS_IsMainThread());returnsActiveVsyncTimers>0;}/* static */boolnsRefreshDriver::GetJankLevels(Vector<uint64_t>&aJank){aJank.clear();returnaJank.append(sJankLevels,ArrayLength(sJankLevels));}#undef LOG